/*=============================================================================
*
*   Copyright (C) 2020 All rights reserved.
*
*   Filename:		dof.hpp
*
*   Author: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Date: 2020-02-20 03:55
*
*   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Last modified:	2020-03-24 22:38
*
*   Description:
*
=============================================================================*/
#ifndef IDEGREEOFFREEDOM_H
#define IDEGREEOFFREEDOM_H

#include "../arithmetic/time_functions.hpp"
#include "../geometry/shape/dimension_type.hpp"
#include "../io/log.hpp"
#include "../util/indexed.hpp"

namespace geoxfem {
namespace model {

enum class bcType { free, dirichlet, neumann };

struct Dof : util::Indexed {
  using TimeFunc = arithmetic::Time_Function_Interface;

  /*constructor{{{*/

  Dof(const size_t& node = 0)
      : util::Indexed{},
        m_type{bcType::free},
        m_node{node},
        m_value{0},
        m_dvalue{0},
        m_ddvalue{0},
        m_load{0},
        m_bndry_value{0},
        m_ptr_tfunc{nullptr} {}
  Dof(const Dof&) = default;
  Dof(Dof&&)      = default;
  virtual ~Dof()  = default;
  Dof& operator=(const Dof&) = default;
  Dof& operator=(Dof&&) = default;
  /*}}}*/
  /*member direct access{{{*/

  /** get time function if is boundary
   *
   * @return
   */
  TimeFunc* getTimeFunction() const {
    return ifFree() ? nullptr : m_ptr_tfunc;
  }
  /**
   * @brief setTimeFunction
   *
   * @param ptr
   */
  void setTimeFunction(TimeFunc* ptr) {
    m_ptr_tfunc = ptr;
  }
  /**
   * @brief get id of parent node
   *
   * @return
   */
  size_t getNodeID() const {
    return m_node;
  }
  /*}}}*/
  /*access previous absolute value manuvar{{{*/

  /** get absolute value at previous time step
   *
   * @return
   */
  _real getValue() const {
    return m_value;
  }
  /** set absolute value at previous time step
   *
   * @param value
   */
  void setValue(const _real& value) {
    m_value = value;
  }
  /** self increment operator of absolute value at previous time step
   *
   * @param value
   */
  void addValue(const _real& value) {
    m_value += value;
  }
  /*}}}*/
  /*time step value manuvar{{{*/
  /** get delta value at current time step
   *
   * @return
   */
  _real getValueAtTimeStep() const {
    return m_dvalue;
  }
  /** set delta value at current time step
   *
   * @param value
   */
  void setValueAtTimeStep(const _real& value) {
    m_dvalue = value;
  }
  /** self increment operator of delta value at current time step
   *
   * @param value
   */
  void addValueAtTimeStep(const _real& value) {
    m_dvalue += value;
  }
  /*}}}*/
  /*iteration value manuvar{{{*/
  /** get delta value at current time step
   *
   * @return
   */
  _real getValueAtIteration() const {
    return m_ddvalue;
  }
  /** set delta value at current time step
   *
   * @param value
   */
  void setValueAtIteration(const _real& value) {
    m_ddvalue = value;
  }
  /*}}}*/
  /*  get total value at current time/iteration {{{*/
  /** get total value at current time/iteration
   *
   * @return
   */
  _real getCurrValue() const {
    return getValue() + getValueAtTimeStep();
  }
  /*}}}*/
  /*load manuvar{{{*/

  /** get absolute load
   *
   * @return
   */
  _real getExternalLoad() const {
    return m_load;
  }
  /** set absolute load
   *
   * @param value
   */
  void setExternalLoad(const _real& value) {
    m_load = value;
  }
  /*}}}*/
  /*boundary type{{{*/

  /** get boundary condition type
   *
   * @return
   */
  bcType getBCType() const {
    return m_type;
  }
  /** set boundary condition type
   *
   * @param value
   */
  void setBCType(const bcType& type) {
    m_type = type;
  }
  /** if is Dirichlet
   *
   * @return
   */
  bool ifDirichlet() const {
    return m_type == bcType::dirichlet;
  }
  /**
   * @brief getDirichletValue
   *
   * @return
   */
  _real getDirichletValue() const {
    return ifDirichlet() ? m_bndry_value : 0;
  }
  /**
   * @brief setBoundaryValue
   *
   * @param value
   */
  void setBoundaryValue(const _real& value) {
    m_bndry_value = value;
  }
  /**
   * @brief setDirichletValue
   *
   * @param value
   */
  void setDirichletValue(const _real& value) {
    if(ifDirichlet())
      m_bndry_value = value;
  }
  /** if is Neumann
   *
   * @return
   */
  bool ifNeumann() const {
    return m_type == bcType::neumann;
  }
  /**
   * @brief getNeumannValue
   *
   * @return
   */
  _real getNeumannValue() const {
    return ifNeumann() ? m_bndry_value : 0;
  }
  /**
   * @brief setNeumannValue
   *
   * @param value
   */
  void setNeumannValue(const _real& value) {
    if(ifNeumann())
      m_bndry_value = value;
  }
  /**
   * @brief ifFree
   *
   * @return
   */
  bool ifFree() const {
    return m_type == bcType::free;
  }

  /*}}}*/
  /*misc{{{*/
  bool operator==(const Dof& rhs) const {
    return m_type == rhs.m_type && m_node == rhs.m_node
           && getIndex() == rhs.getIndex();
  }

  friend std::ostream& operator<<(std::ostream& os, const Dof& dof) {
    os << "Dof #" << dof.getIndex() << " at node" << dof.getNodeID();
    return os;
  }
  /*}}}*/

private:
  bcType    m_type;
  size_t    m_node;
  _real     m_value;
  _real     m_dvalue;
  _real     m_ddvalue;
  _real     m_load;
  _real     m_bndry_value;
  TimeFunc* m_ptr_tfunc;
};

/*functor to access dof member{{{*/
struct dof_value_at_prev_tstep {
  static _real apply(const Dof& dof) {
    return dof.getValue();
  }
  static _real apply(const Dof* dof) {
    return dof->getValue();
  }
};
struct dof_value_at_curr_tstep {
  static _real apply(const Dof& dof) {
    return dof.getValueAtTimeStep();
  }
  static _real apply(const Dof* dof) {
    return dof->getValueAtTimeStep();
  }
};
struct dof_value_at_curr_iter {
  static _real apply(const Dof& dof) {
    return dof.getValueAtIteration();
  }
  static _real apply(const Dof* dof) {
    return dof->getValueAtIteration();
  }
};
struct dof_curr_value {
  static _real apply(const Dof& dof) {
    return dof.getCurrValue();
  }
  static _real apply(const Dof* dof) {
    return dof->getCurrValue();
  }
};
struct dof_load {
  static _real apply(const Dof& dof) {
    return dof.getExternalLoad();
  }
  static _real apply(const Dof* dof) {
    return dof->getExternalLoad();
  }
};
struct dof_dirichlet {
  static _real apply(const Dof& dof) {
    return dof.getDirichletValue();
  }
  static _real apply(const Dof* dof) {
    return dof->getDirichletValue();
  }
};
struct dof_dirichlet_residual {
  static _real apply(const Dof& dof) {
    return dof.getDirichletValue() - dof.getCurrValue();
  }
  static _real apply(const Dof* dof) {
    return dof->getDirichletValue() - dof->getCurrValue();
  }
};
struct dof_neumann {
  static _real apply(const Dof& dof) {
    return dof.getNeumannValue();
  }
  static _real apply(const Dof* dof) {
    return dof->getNeumannValue();
  }
};
/*}}}*/
}  // namespace model
}  // namespace geoxfem

bool operator==(const std::unique_ptr<geoxfem::model::Dof>& lhs,
                const std::unique_ptr<geoxfem::model::Dof>& rhs) {
  return *lhs == *rhs;
}

#endif /* IDEGREEOFFREEDOM_H */
